﻿using MyHome.Server.PluginBase;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace MyHome.Server.PluginManagement
{
    /// <summary>
    /// Helper class to load plugins from the database.
    /// </summary>
    internal static class PluginDatabaseHelper
    {
        private static DataAccess.Repository repository;

        static PluginDatabaseHelper()
        {
            // TODO: Add connection string
            repository = new DataAccess.Repository();
        }

        internal static IPlugin AddPlugin(Type type, int id, IDictionary<string, string> configuration, int x, int y)
        {
            var plugin = CreatePlugin(type, id, configuration, x, y);
            repository.AddPlugin(plugin);
            return plugin;
        }

        internal static IPluginConnection<object> AddPluginConnection(int id, IPlugin input, IPlugin output)
        {
            var pluginConnection = CreatePluginConnection(id, input, output);
            repository.AddPluginConnection(pluginConnection);
            return pluginConnection;
        }

        internal static void DeletePlugin(int id)
        {
            repository.DeletePlugin(id);
        }

        internal static void DeletePluginConnection(int id)
        {
            repository.DeletePluginConnection(id);
        }

        /// <summary>
        /// Loads the plugin connections from the database asynchronous.
        /// </summary>
        /// <param name="plugins">The list of all plugins.</param>
        /// <returns>A list of all plugin connections</returns>
        internal static Task<IEnumerable<IPluginConnection<object>>> LoadPluginConnectionsFromDatabaseAsync(IEnumerable<IPlugin> plugins)
        {
            return Task.Run(() =>
            {
                Trace.TraceInformation("Loading plugin connections from database");

                var pluginConnections = repository.GetPluginConnections().AsEnumerable();
                foreach (var connection in pluginConnections)
                {
                    connection.Input = plugins.Single(p => p.Id == connection.InputId) as IInputPlugin<object>;
                    connection.Output = plugins.Single(p => p.Id == connection.OutputId) as IOutputPlugin<object>;
                }
                
                Trace.TraceInformation("Finished loading plugin connections from database");

                return pluginConnections;
            });
        }

        /// <summary>
        /// Loads the plugins from the database asynchronous.
        /// </summary>
        /// <param name="pluginTypes">The list of all plugin types.</param>
        /// <returns>A list of all plugins.</returns>
        internal static Task<IEnumerable<IPlugin>> LoadPluginsFromDatabaseAsync(IDictionary<string, Type> pluginTypes)
        {
            return Task.Run(() =>
            {
                Trace.TraceInformation("Loading plugins from database");
                
                repository.PluginTypes = pluginTypes;
                var plugins = repository.GetPlugins().AsEnumerable();

                Trace.TraceInformation("Finished loading plugins from database");

                return plugins;
            });
        }

        internal static void UpdatePlugin(IPlugin plugin)
        {
            repository.UpdatePlugin(plugin);
        }

        internal static void UpdatePluginConnection(IPluginConnection<object> pluginConnection)
        {
            repository.UpdatePluginConnection(pluginConnection);
        }

        private static IPlugin CreatePlugin(Type type, int id, IDictionary<string, string> configuration, int x, int y)
        {
            try
            {
                IPlugin plugin;
                var constructor = type.GetConstructor(new Type[0]);
                var obj = constructor.Invoke(null);
                obj = Activator.CreateInstance(type);
                plugin = obj as IPlugin;
                if (plugin == null)
                    throw new ArgumentException("The type " + type.FullName + " does not inherit from IPlugin.");

                plugin.Id = id;
                plugin.Configuration = configuration;
                plugin.X = x;
                plugin.Y = y;

                Trace.TraceInformation("Successfully created the plugin of type {0} with the id {1} and the configuration\n{2}\nfrom the database.", type.FullName, id, configuration == null ? "null" : string.Join(";", configuration));

                return plugin;
            }
            catch (Exception e)
            {
                Trace.TraceError("An exception occured while creating the plugin of type {0} with the id {1} and the configuration\n{2}\nThe exception was:\n{3}", type.FullName, id, configuration == null ? "null" : string.Join(";", configuration), e.ToString());
                return null;
            }
        }

        private static IPluginConnection<object> CreatePluginConnection(int id, IPlugin input, IPlugin output)
        {
            var pluginConnection = new PluginConnection<object>() { Id = id };

            var inputPlugin = input as IInputPlugin<object>;
            if (inputPlugin != null)
                pluginConnection.Input = inputPlugin;
            else
                Trace.TraceError("An exception occured while creating the PluginConnection with id {0} from {1} to {2}. {1} is no InputPlugin.", id, input, output);

            var outputPlugin = output as IOutputPlugin<object>;
            if (outputPlugin != null)
                pluginConnection.Output = outputPlugin;
            else
                Trace.TraceError("An exception occured while creating the PluginConnection with id {0} from {1} to {2}. {2} is no OutputPlugin.", id, input, output);

            Trace.TraceInformation("Created the PluginConnection with id {0} from {1} to {2}.", id, input, output);
            return pluginConnection;
        }

        private static IPlugin GetPluginById(IEnumerable<IPlugin> plugins, int id)
        {
            var possible = plugins.Where(p => p.Id == id);
            if (possible.Count() == 0)
                return null;

            return possible.First();
        }
    }
}